//////////////////////////////////////////////////////////////////////////

#ifndef _CLIENTCONNECTIONIMPL_H_
#include "clientconnectionimpl.h"
#endif

//////////////////////////////////////////////////////////////////////////

#ifndef _INNERPACKET_H_
#include "innerpacket.h"
#endif

#ifndef _TIMER_H_
#include "timer.h"
#endif

#ifndef _BOOSTSERVICE_H_
#include "boostservice.h"
#endif

#include <boost/bind.hpp>
#include <iostream>

//////////////////////////////////////////////////////////////////////////


ClientConnectionImpl::ClientConnectionImpl()
: m_TCPSocket( BoostService::GetIOService() ),
  m_UDPSocket( BoostService::GetIOService() ),
  m_UDPConfirmed(false),
  m_IsEndpointReceived(false)
{
}


//////////////////////////////////////////////////////////////////////////


ClientConnectionImpl::~ClientConnectionImpl()
{
}


//////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::SendTCPInner( const NetworkPacket& _data )
{
    // Send data through TCP
    m_TCPSocket.async_send( boost::asio::buffer( _data.Data(), _data.Size() ),
        boost::bind( &ClientConnectionImpl::HandleSendTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );
}


//////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::SendUDPInner( const NetworkPacket& _data )
{
    // Send data through UDP
    m_UDPSocket.send_to( boost::asio::buffer( _data.Data(), _data.Size() ), m_UDPEndPoint );
    ConnectionImpl::HandleSendUDP();
}


//////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::SendTCP( const NetworkPacket& _data )
{
    // Create inner packet from user packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // Send inner packet
    SendTCPInner(innerPacket);
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::SendUDP( const NetworkPacket& _data )
{
    // Ensure that UDP is confirmed before sending
    if ( !IsUDPConfirmed() )
    {
        ConfirmUDP();
        return;
    }

    // Create inner packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // And send it
    SendUDPInner(innerPacket);
}



////////////////////////////////////////////////////////////////////////////////


bool ClientConnectionImpl::IsUDPConfirmed()
{
    return m_UDPConfirmed;
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::ConfirmUDP()
{
    // Cannot confirm connection
    if ( !m_IsEndpointReceived )
        return;

    // Until UDP connection between server and client is not confirmed,
    // send confirmation packets.
    // Confirmation packet consists of confirmation string, which was
    // received from server via TCP

    // Create confirmation packet
    InnerPacket confirm(m_Confirmation, InnerPacket::CONNECT);
    confirm.PackCode();

    // Send packet
    SendUDPInner(confirm);
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::StartReceiveTCP()
{
    // Recv data through TCP
    m_TCPSocket.async_receive( boost::asio::buffer(m_RecvBufferTCP, MAX_BUFFER_SIZE),
        boost::bind( &ClientConnectionImpl::HandleRecvTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::StartReceiveUDP()
{
    // Recv data through UDP
    m_UDPSocket.async_receive_from( boost::asio::buffer(m_RecvBufferUDP, MAX_BUFFER_SIZE),
        m_UDPEndPoint,
        boost::bind( &ClientConnectionImpl::HandleRecvUDP,
        this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::HandleSendTCP( const error_code& _error,
                                          size_t _bytesTransferred )
{
    // Check for errors
    if (_error)
    {
        std::cout << "ERROR SEND TCP: " << _error.message() << std::endl;
        return;
    }

    ConnectionImpl::HandleSendTCP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::HandleRecvTCP( const error_code& _error, size_t _bytesTransferred )
{
    // Check for errors
    if (_error)
    {
        std::cout << "ERROR RECV TCP: " << _error.message() << std::endl;
        return;
    }


    // Compose inner packet from data buffer
    InnerPacket innerPacket(m_RecvBufferTCP, _bytesTransferred);

    // Get its code
    switch ( innerPacket.UnPackCode() )
    {
    // Regular packet with server data
    case InnerPacket::REGULAR:
        HandleRecvTCPRegular(innerPacket);
        break;

    // Connect packet with connection string
    case InnerPacket::CONNECT:
        HandleRecvTCPConnect(innerPacket);
        break;

    // Confirmation from server!
    case InnerPacket::CONFIRM:
        m_UDPConfirmed = true;
        break;

    default:
        assert(!"Invalid packet type");
        break;
    }

    // Start receive TCP again
    StartReceiveTCP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::HandleRecvTCPRegular( const InnerPacket& _innerPacket )
{
    m_RecvPacketTCP = _innerPacket;
    ConnectionImpl::HandleRecvTCP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::HandleRecvTCPConnect( const InnerPacket& _innerPacket )
{
    // Confirm UDP connection
    m_Confirmation = _innerPacket;
    m_IsEndpointReceived = true;

    std::cout << "TCP connect confirmation received: " << m_Confirmation.Data() << std::endl;

    ConfirmUDP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::HandleRecvUDP( const error_code& _error, size_t _bytesTransferred )
{
    // Check for errors
    if (_error)
    {
        // Some strange bugs...
        if (_error.value() == 10022 || _error.value() == 10061)
            StartReceiveUDP();
        else
        {
            assert(!"Unknown UDP receive error");
            std::cout << "ERROR RECV UDP: " << _error.message() << std::endl;
        }

        return;
    }


    // Compose inner packet from data buffer
    InnerPacket innerPacket(m_RecvBufferUDP, _bytesTransferred);

    // Get its code
    switch ( innerPacket.UnPackCode() )
    {
    // Regular packet with server data
    case InnerPacket::REGULAR:
        HandleRecvUDPRegular(innerPacket);
        break;

    default:
        assert(!"Invalid packet type");
        break;
    }

    // Start receive UDP again
    StartReceiveUDP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::HandleRecvUDPRegular( const InnerPacket& _innerPacket )
{
    m_RecvPacketUDP = _innerPacket;
    ConnectionImpl::HandleRecvUDP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::Accept()
{
    // Empty implementation here, Accept() is for server only, not for client
    throw "Should never reach here!";
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::Connect( const char* _address )
{
    // If successfully connected over TCP
    if ( EstablishTCP(_address) )
    {
        // Establish UDP connection
        EstablishUDP(_address);

        // Start receiving TCP
        StartReceiveTCP();
    }
}


////////////////////////////////////////////////////////////////////////////////


bool ClientConnectionImpl::EstablishTCP( const char* _address )
{
    // Resolve address
    tcp::resolver resolver( BoostService::GetIOService() );
    tcp::resolver::query query(_address, m_PortStr);
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    tcp::resolver::iterator end;

    error_code error = boost::asio::error::host_not_found;
    while (error && endpoint_iterator != end)
    {
        m_TCPSocket.close();

        // Connect to server
        m_TCPSocket.connect(*endpoint_iterator, error);

        ++endpoint_iterator;
    }

    // Check for errors
    if (error)
    {
        std::cout << "ERROR CONNECT TCP: " << error.message() << std::endl;
        return false;
    }
  
 
    return true;
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::EstablishUDP( const char* _address )
{
    // Resolve server address
    udp::resolver resolver( BoostService::GetIOService() );
    udp::resolver::query query( udp::v4(), _address, m_PortStr );
    m_UDPEndPoint = *resolver.resolve(query);

    boost::system::error_code error;

    // Open UDP socket
    m_UDPSocket.open( udp::v4(), error );

    if (error)
    {
        std::cout << "Error opening UDP socket: " << error.message() << std::endl;
        return;
    }

    // Start receive over UDP
    StartReceiveUDP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnectionImpl::Close()
{
    m_TCPSocket.close();
}


////////////////////////////////////////////////////////////////////////////////


bool ClientConnectionImpl::IsConnected() const
{
    return m_UDPConfirmed && m_TCPSocket.is_open();
}


////////////////////////////////////////////////////////////////////////////////
